using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;

namespace Decav.Collections
{
    /// <summary>
    /// A collection, or decorator of another collection, that provides the ability to make
    /// changes to a collection and commit or rollback those changes prior to making a change
    /// to the underlying data structure.
    /// </summary>
    /// <typeparam name="T">The type of items in the collection.</typeparam>
    [Serializable]
    public class TransactionalCollection<T> : IList<T>, ICollection<T>, ITransactionalCollection<T>
    {
        /// <summary>
        /// Initialzies a new <see cref="TransactionalCollection`1"/> that provides its
        /// own
        /// </summary>
        public TransactionalCollection()
            : this (new Collection<T>())
        {
        }

        /// <summary>
        /// Initializes a new <see cref="TransactionalCollection`1"/> that wraps another
        /// collection and provides transactional support around that collection.
        /// </summary>
        /// <param name="wrappedCollection">The collection to provide transactional support
        /// for.</param>
        public TransactionalCollection(IList<T> wrappedCollection)
        {
            if (wrappedCollection == null)
                throw new ArgumentNullException("wrappedCollection");

            _wrapped = wrappedCollection;
            _wrappedReadOnly = new ReadOnlyCollection<T>(_wrapped);
            Initialize();
        }

        /// <summary>
        /// Reinitializes the <see cref="_currentItems"/> with the items from the
        /// <see cref="_wrapped"/> list.
        /// </summary>
        private void Initialize()
        {
            _currentItems.Clear();

            foreach (T item in _wrapped)
                _currentItems.Add(item);
        }

        /// <summary>
        /// The collection that the <see cref="TransactionalCollection`1"/> wraps and provides
        /// transactional support for.
        /// </summary>
        private IList<T> _wrapped;

        /// <summary>
        /// The current items in the collection, as if the transaction was committed.
        /// </summary>
        private readonly Collection<T> _currentItems = new Collection<T>();

        /// <summary>
        /// The commands to be executed against the wrapped version of the collection.
        /// </summary>
        private readonly Queue<CollectionCommand<T>> _commands = new Queue<CollectionCommand<T>>();

        private ReadOnlyCollection<T> _wrappedReadOnly = null;

        /// <summary>
        /// Gets a collection of all the items that have been committed.
        /// </summary>
        public ReadOnlyCollection<T> CommittedItems
        {
            get
            {
                return _wrappedReadOnly;
            }
        }

        #region Methods

        /// <summary>
        /// Enqueues a command to operate against the wrapped collection on commit.
        /// </summary>
        /// <param name="command">The command to enqueue.</param>
        private void EnqueueCommand(CollectionCommand<T> command)
        {
            _commands.Enqueue(command);
        }

        #endregion

        #region ITransactionalCollection<T> Members

        /// <summary>
        /// Gets if there are any changes that have not been committed to the underlying
        /// collection.
        /// </summary>
        public bool HasChanges
        {
            get
            {
                return _commands.Count > 0;
            }
        }

        /// <summary>
        /// Commits the changes made to the <see cref="TransactionalCollection`1"/>
        /// to the underlying collection.
        /// </summary>
        public void CommitChanges()
        {
            while (_commands.Count > 0)
                _commands.Dequeue().Execute(_wrapped);
        }

        /// <summary>
        /// Rolls back the changes made to the <see cref="TransactionalCollection`1"/>
        /// so they weill never be committed to the underlying collection, and restores
        /// the <see cref="TransactionalCollection`1"/> to the state of the underlying
        /// collection.
        /// </summary>
        public void RollbackChanges()
        {
            _commands.Clear();
            Initialize();
        }

        #endregion

        #region ICollection<T> Members

        /// <summary>
        /// Adds a new item as an uncommitted item in the collection.
        /// </summary>
        /// <param name="item">The item to add to the collection.</param>
        public void Add(T item)
        {
            if (item == null)
                throw new ArgumentNullException("item");

            _currentItems.Add(item);
            EnqueueCommand(new AddCollectionCommand<T>(item));
        }

        /// <summary>
        /// Clears the collection.
        /// </summary>
        public void Clear()
        {
            _currentItems.Clear();
            EnqueueCommand(new ClearCollectionCommand<T>());
        }

        /// <summary>
        /// Checks if an item is contained in the uncommitted version of the collection.
        /// </summary>
        /// <param name="item">The item to check for.</param>
        /// <returns>True when the item is in the uncommitted collection, else false.</returns>
        public bool Contains(T item)
        {
            if (item == null)
                throw new ArgumentNullException("item");

            return _currentItems.Contains(item);
        }

        /// <summary>
        /// Copies the uncommitted version of the collection to an array.
        /// </summary>
        /// <param name="array">The array to copy items to.</param>
        /// <param name="arrayIndex">The index to begin copy at.</param>
        public void CopyTo(T[] array, int arrayIndex)
        {
            if (array == null)
                throw new ArgumentNullException("array");

            _currentItems.CopyTo(array, arrayIndex);
        }

        /// <summary>
        /// Gets the count of items in the uncommitted version of the collection.
        /// </summary>
        public int Count
        {
            get
            {
                return _currentItems.Count;
            }
        }

        bool ICollection<T>.IsReadOnly
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// Removes an item from the uncommitted version of the collection.
        /// </summary>
        /// <param name="item">The item to remove.</param>
        /// <returns>True when the item was in the collection and removed,
        /// else false.</returns>
        public bool Remove(T item)
        {
            if (item == null)
                throw new ArgumentNullException("item");

            bool removed = _currentItems.Remove(item);

            if (removed)
                EnqueueCommand(new RemoveCollectionCommand<T>(item));

            return removed;
        }

        #endregion

        #region IEnumerable<T> Members

        /// <summary>
        /// Gets an enumerator to enumerate the uncommitted items in the collection.
        /// </summary>
        /// <returns>An enumerator that enumerates uncommitted items</returns>
        public IEnumerator<T> GetEnumerator()
        {
            return _currentItems.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        /// <summary>
        /// Gets an enumerator to enumerate the uncommitted items in the collection.
        /// </summary>
        /// <returns>An enumerator that enumerates uncommitted items</returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return _currentItems.GetEnumerator();
        }

        #endregion

        #region IList<T> Members

        /// <summary>
        /// Checks the index of an item in the uncommitted version of the collection.
        /// </summary>
        /// <param name="item">The item to check the index of.</param>
        /// <returns>The index of the specified item.</returns>
        public int IndexOf(T item)
        {
            return _currentItems.IndexOf(item);
        }

        /// <summary>
        /// Inserts an item at the specified index into the uncommitted version of the collection.
        /// </summary>
        /// <param name="index">The index to insert into.</param>
        /// <param name="item">The item to insert at that index.</param>
        public void Insert(int index, T item)
        {
            _wrapped.Insert(index, item);
            EnqueueCommand(new InsertCollectionCommand<T>(index, item));
        }

        /// <summary>
        /// Removes an item at the specified index of the uncommitted version of the collection.
        /// </summary>
        /// <param name="index">The index to remove the item at.</param>
        public void RemoveAt(int index)
        {
            _wrapped.RemoveAt(index);
            EnqueueCommand(new RemoveAtCollectionCommand<T>(index));
        }

        /// <summary>
        /// Gets or sets the value of a specific index of the uncommitted version of the collection.
        /// </summary>
        /// <param name="index">The index of the collection to get or set.</param>
        /// <returns>The value at the specified index.</returns>
        public T this[int index]
        {
	        get 
	        {
                return _currentItems[index];
	        }
	        set 
	        {
                _currentItems[index] = value;
	        }
        }

        #endregion

        #region Commands
        [Serializable]
        private abstract class CollectionCommand<TItem>
        {
            public CollectionCommand()
            {
            }

            public abstract void Execute(IList<TItem> collection);
        }

        [Serializable]
        private class RemoveCollectionCommand<TItem> : CollectionCommand<TItem>
        {
            public RemoveCollectionCommand(TItem value)
            {
                _value = value;
            }

            private TItem _value;

            public override void Execute(IList<TItem> collection)
            {
                collection.Remove(_value);
            }
        }

        [Serializable]
        private class AddCollectionCommand<TItem> : CollectionCommand<TItem>
        {
            public AddCollectionCommand(TItem value)
            {
                _value = value;
            }

            private TItem _value;

            public override void Execute(IList<TItem> collection)
            {
                collection.Add(_value);
            }
        }

        [Serializable]
        private class InsertCollectionCommand<TItem> : CollectionCommand<TItem>
        {
            public InsertCollectionCommand(int index, TItem value)
            {
                _index = index;
                _value = value;
            }

            private int _index;
            private TItem _value;

            public override void Execute(IList<TItem> collection)
            {
                collection.Insert(_index, _value);
            }
        }

        [Serializable]
        private class ClearCollectionCommand<TItem> : CollectionCommand<TItem>
        {
            public ClearCollectionCommand()
            {
            }

            public override void Execute(IList<TItem> collection)
            {
                collection.Clear();
            }
        }

        [Serializable]
        private class RemoveAtCollectionCommand<TItem> : CollectionCommand<TItem>
        {
            public RemoveAtCollectionCommand(int index)
            {
                _index = index;
            }

            private int _index;

            public override void Execute(IList<TItem> collection)
            {
                collection.RemoveAt(_index);
            }
        }

        [Serializable]
        private class ChangeCollectionCommand<TItem> : CollectionCommand<TItem>
        {
            public ChangeCollectionCommand(int index, TItem newValue)
            {
                _index = index;
                _newValue = newValue;
            }

            private int _index;
            private TItem _newValue;

            public override void Execute(IList<TItem> collection)
            {
                collection[_index] = _newValue;
            }
        }
        #endregion
    }
}
